/*
 * @Author: 张宇飞
 * @Date: 2020-02-21 17:51:10
 * @LastEditors: 张宇飞
 * @LastEditTime: 2020-03-02 13:19:54
 * @Description: 读写锁。特点是：写独占，读共享。 有三种状态：
 * 1. 读状态下加锁（读锁）
 * 2. 写状态下加锁（写锁）
 * 3. 不加锁
 *
 * 性质：
 * 1. 上读锁的时候，其他线程再加读锁都会成功。但是上写锁会阻塞
 * 2. 上写锁的时候，其他线程只要加锁就会阻塞
 * 3. 已经上了读锁，其他线程既有试图上读锁又有试图上写锁的
 * 调度的顺序取决于锁的类型。详细见 man 3 pthread_rwlockattr_setkind_np
 *
 * 综上，读写锁适合读多写少的场景。例如大多数数据库操作
 * @Email: zhangyufei49@163.com
 */
#include <assert.h>
#include <pthread.h>
#include <stdio.h>
#include <string.h>
#include <unistd.h>

#include "utils.h"

// 全局计数器
static int gcount = 0;
#define show_gcount() fprintf(stderr, "\tin %s gcount = %d\n", __func__, gcount)

static pthread_rwlock_t glock;
static pthread_rwlockattr_t gattr;

#define print_test_header() \
    gcount = 0;             \
    fprintf(stderr, "\n------------ %s ------------\n\n", __func__)

#define rdlock(ret)                      \
    ret = pthread_rwlock_rdlock(&glock); \
    fprintf(stderr, "%s 加读锁%s\n", __func__, ret == 0 ? "成功" : "失败")
#define rdunlock()                 \
    pthread_rwlock_unlock(&glock); \
    fprintf(stderr, "%s 释放了读锁\n", __func__)
#define wrlock(ret)                      \
    ret = pthread_rwlock_wrlock(&glock); \
    fprintf(stderr, "%s 加写锁%s\n", __func__, ret == 0 ? "成功" : "失败")
#define wrunlock()                 \
    pthread_rwlock_unlock(&glock); \
    fprintf(stderr, "%s 释放了写锁\n", __func__)

// 上读锁
#define r(arg, after_show_sleep_secs)             \
    fprintf(stderr, "%s 开始上读锁\n", __func__); \
    int r;                                        \
    rdlock(r);                                    \
    show_gcount();                                \
    sleep(after_show_sleep_secs);                 \
    rdunlock();                                   \
    return NULL

// 上写锁

#define w(arg, after_show_sleep_secs)             \
    fprintf(stderr, "%s 开始上写锁\n", __func__); \
    int r;                                        \
    wrlock(r);                                    \
    gcount++;                                     \
    show_gcount();                                \
    sleep(after_show_sleep_secs);                 \
    wrunlock();                                   \
    return NULL

static void* r1(void* arg) { r(arg, 5); }
static void* r2(void* arg) {
    sleep(1);
    r(arg, 0);
}

static void* r3(void* arg) {
    sleep(2);
    r(arg, 0);
}

static void* r4(void* arg) {
    show_gcount();
    return NULL;
}

static void* r5(void* arg) {
    sleep(1);
    fprintf(stderr, "%s 开始非堵塞上读锁\n", __func__);
    // trylock 这里会返回 EBUSY
    int ret = pthread_rwlock_tryrdlock(&glock);
    fprintf(stderr, "%s 非阻塞加读锁%s\n", __func__,
            ret == 0 ? "成功" : strerror(ret));
    if (!ret) {
        rdunlock();
    }
    return NULL;
}

static void* w1(void* arg) {
    sleep(2);
    w(arg, 0);
}
static void* w2(void* arg) { w(arg, 5); }

static void* w3(void* arg) {
    sleep(1);
    w(arg, 0);
}

static void* w4(void* arg) {
    sleep(1);
    fprintf(stderr, "%s 开始非堵塞上写锁\n", __func__);
    // trylock 这里会返回 EBUSY
    int ret = pthread_rwlock_trywrlock(&glock);
    fprintf(stderr, "%s 非阻塞加写锁%s\n", __func__,
            ret == 0 ? "成功" : strerror(ret));
    if (!ret) {
        wrunlock();
    }
    return NULL;
}

// 测试上读锁的情况
static void test_rlock() {
    print_test_header();
    fprintf(stderr, "上了读锁的时候，再上读锁不阻塞，上写锁阻塞\n\n");
    run_threads(r1, r2, w1, NULL);
}

// 测试上写锁的情况
static void test_wlock() {
    print_test_header();
    fprintf(stderr, "只要上了写锁，再上任何锁就会阻塞\n\n");
    run_threads(w2, r2, w1, NULL);
}

// 测试上读锁的时候其他锁的调度顺序
static void test_rlock_priority() {
    print_test_header();
    int wf;
    pthread_rwlockattr_getkind_np(&gattr, &wf);
    fprintf(stderr, "上读锁的时候，其它线程有请求读写锁的，%s请求先调度\n",
            wf == PTHREAD_RWLOCK_PREFER_READER_NP ? "读" : "写");
    run_threads(r1, w3, r3, NULL);
}

// 测试上写锁的时候其他锁的调度顺序
static void test_wlock_priority() {
    print_test_header();
    int wf;
    pthread_rwlockattr_getkind_np(&gattr, &wf);
    fprintf(stderr, "上写锁的时候，其它线程有请求读写锁的，%s请求先调度\n",
            wf == PTHREAD_RWLOCK_PREFER_READER_NP ? "读" : "写");
    run_threads(w2, w3, r3, NULL);
}

static void test_nolock() {
    print_test_header();
    fprintf(stderr,
            "不加锁读取数据的情况下，你会发现 r4 有可能出现打印 0 "
            "的情况\n这说明，读写锁也是建议锁（看不到 0 请多次运行程序）\n\n");
    run_threads(w2, r4, r4, r4, r4, NULL);
}

static void test_trylock() {
    print_test_header();
    run_threads(w2, r5, w4, NULL);
    fprintf(stderr, "\n\n");
    run_threads(r1, r5, w4, NULL);
}

int main(int argc, char const* argv[]) {
    // PTHREAD_RWLOCK_PREFER_READER_NP 是默认的。优先写的话需要置
    // PTHREAD_RWLOCK_PREFER_WRITER_NONRECURSIVE_NP
    int prefer = PTHREAD_RWLOCK_PREFER_READER_NP;
    if (argc > 1) {
        if (strcmp("wf", argv[1]) == 0) {
            prefer = PTHREAD_RWLOCK_PREFER_WRITER_NONRECURSIVE_NP;
        } else {
            printf("usage: %s [wf]\n", argv[0]);
            printf("默认调度为读优先，如果参数 wf 设置的话，将启用写优先\n");
            return -1;
        }
    }
    assert(0 == pthread_rwlockattr_init(&gattr));
    assert(0 == pthread_rwlockattr_setkind_np(&gattr, prefer));
    assert(0 == pthread_rwlock_init(&glock, &gattr));

    test_rlock();
    test_wlock();
    test_rlock_priority();
    test_wlock_priority();
    test_nolock();
    test_trylock();

    pthread_rwlock_destroy(&glock);
    pthread_rwlockattr_destroy(&gattr);
    return 0;
}
